Webhook - #490
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces an activity-log–driven webhook system (DB outbox + dispatcher) and exposes management + event-type discovery through the GraphQL API. This extends the existing activitylog infrastructure by formalizing “activity types” with webhook-facing metadata (description/group/scope) and wiring a dispatcher into the API runtime.
Changes:
- Added webhook subscription + delivery pipeline (Postgres outbox tables, dispatcher, retries/backoff, pruning, metrics, signing, CloudEvents payloads).
- Exposed webhook CRUD + listing + event-type catalogue via GraphQL schema and resolvers.
- Refactored activity log registration from
RegisterFiltertoRegisterActivityTypeand added webhook metadata options (description/group/global-only/ignore).
Reviewed changes
Copilot reviewed 50 out of 54 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/workload/secret/activitylog.go | Migrates secret activity types to RegisterActivityType with descriptions. |
| internal/workload/job/activitylog.go | Migrates job activity types to RegisterActivityType with descriptions. |
| internal/workload/config/activitylog.go | Migrates config activity types to RegisterActivityType with descriptions. |
| internal/workload/application/activitylog.go | Migrates application activity types to RegisterActivityType with descriptions. |
| internal/vulnerability/activitylog.go | Migrates vulnerability activity type to RegisterActivityType with description. |
| internal/unleash/activitylog.go | Migrates Unleash activity types to RegisterActivityType (ignores webhooks). |
| internal/tunnel/activitylog.go | Migrates tunnel activity types to RegisterActivityType (ignores webhooks). |
| internal/team/activitylog.go | Migrates team activity types to RegisterActivityType, adds GlobalOnly where relevant. |
| internal/serviceaccount/activitylog.go | Migrates service account activity types to RegisterActivityType with descriptions. |
| internal/reconciler/activitylog.go | Migrates reconciler activity types to RegisterActivityType (GlobalOnly). |
| internal/persistence/valkey/activitylog.go | Migrates Valkey activity types to RegisterActivityType with descriptions. |
| internal/persistence/postgres/activitylog.go | Migrates Postgres activity types to RegisterActivityType with descriptions. |
| internal/persistence/opensearch/activitylog.go | Migrates OpenSearch activity types to RegisterActivityType with descriptions. |
| internal/kubernetes/event/pubsublog/activitylog.go | Migrates cluster audit activity type to RegisterActivityType (GlobalOnly). |
| internal/integration/manager.go | Updates Graph runner construction to pass webhookDispatcher (nil in tests). |
| internal/graph/webhooks.resolvers.go | Adds GraphQL resolvers for webhook CRUD, listing, deliveries, and event type catalogue. |
| internal/graph/schema/webhooks.graphqls | Adds GraphQL schema for webhook subscriptions, deliveries, inputs/payloads, and queries/mutations. |
| internal/graph/gengql/teams.generated.go | Generated: adds Team.webhooks field plumbing. |
| internal/graph/gengql/schema.generated.go | Generated: adds webhook queries/mutations and node unions. |
| internal/graph/gengql/prelude.generated.go | Generated: adds marshal/unmarshal support for ActivityLogActivityType. |
| internal/graph/gengql/complexity.go | Generated: adds complexity functions for webhook connections. |
| internal/github/repository/activitylog.go | Migrates repository activity types to RegisterActivityType with descriptions. |
| internal/deployment/deploymentactivity/activitylog.go | Migrates deploy-key activity type to RegisterActivityType. |
| internal/database/migrations/0072_webhooks.sql | Adds webhook tables (subscriptions/outbox/queue/audit), trigger, and authorizations. |
| internal/cmd/api/http.go | Wires webhook loader context + dispatcher into request context. |
| internal/cmd/api/api.go | Starts webhook dispatcher + cleaner during API startup and passes dispatcher to Graph config. |
| internal/auth/authz/queries.go | Adds authorization helpers for webhook create/update/delete. |
| internal/activitylog/webhook/webhooksql/webhook.sql.go | Generated (sqlc): webhook SQL query implementations. |
| internal/activitylog/webhook/webhooksql/querier.go | Generated (sqlc): Querier interface for webhook SQL. |
| internal/activitylog/webhook/webhooksql/models.go | Generated (sqlc): webhook SQL models/enums. |
| internal/activitylog/webhook/webhooksql/db.go | Generated (sqlc): DB wrapper for webhook SQL. |
| internal/activitylog/webhook/signer.go | Adds HMAC-SHA256 signing for webhook payloads. |
| internal/activitylog/webhook/signer_test.go | Unit tests for signing helper. |
| internal/activitylog/webhook/README.md | Documents the webhook system architecture and operational characteristics. |
| internal/activitylog/webhook/queries/webhook.sql | Defines sqlc queries for webhook subscriptions, outbox, queue, and audits. |
| internal/activitylog/webhook/queries.go | Implements webhook CRUD/listing, masking, and event-type validation. |
| internal/activitylog/webhook/queries_test.go | Unit tests for secret masking and event-type validation. |
| internal/activitylog/webhook/node.go | Adds Node ident registration for webhook subscription/delivery nodes. |
| internal/activitylog/webhook/node_test.go | Unit tests for webhook ident round-trips and error cases. |
| internal/activitylog/webhook/model.go | Defines webhook domain models and matching logic. |
| internal/activitylog/webhook/model_test.go | Unit tests for subscription/event matching behavior. |
| internal/activitylog/webhook/metrics.go | Adds OpenTelemetry metrics for dispatcher + queue gauge. |
| internal/activitylog/webhook/dispatcher.go | Implements outbox drain, fan-out, delivery, retry/backoff, and ping logic. |
| internal/activitylog/webhook/dispatcher_integration_test.go | Integration tests covering end-to-end dispatcher behavior with Postgres + HTTP servers. |
| internal/activitylog/webhook/dataloader.go | Adds dataloaders and DB access wiring for GraphQL. |
| internal/activitylog/webhook/cloudevents.go | Builds CloudEvents envelopes and maps activity types to io.nais.* type strings. |
| internal/activitylog/webhook/cloudevents_test.go | Unit tests for CloudEvents envelope + type mapping. |
| internal/activitylog/webhook/cleaner.go | Adds leader-only periodic pruning of webhook tables. |
| internal/activitylog/filter.go | Introduces RegisterActivityType + webhook metadata options + event type catalogue functions. |
| internal/activitylog/filter_test.go | Adds tests for activity type registration behavior and webhook metadata utilities. |
| .configs/sqlc.yaml | Adds sqlc generation target for webhook SQL. |
| .configs/gqlgen.yaml | Adds webhook package to gqlgen autobind list. |
Files not reviewed (4)
- internal/activitylog/webhook/webhooksql/db.go: Generated file
- internal/activitylog/webhook/webhooksql/models.go: Generated file
- internal/activitylog/webhook/webhooksql/querier.go: Generated file
- internal/activitylog/webhook/webhooksql/webhook.sql.go: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // The CloudEvent id is derived from the delivery row's own id, so it stays stable across retries. | ||
| payload, err := BuildCloudEvent(d.source, del.ID.String(), event) | ||
| if err != nil { | ||
| d.log.WithError(err).Error("building CloudEvent payload") | ||
| return | ||
| } |
| desc, grp := autoGroupAndDescription(activityType) | ||
| info := &WebhookEventTypeInfo{ | ||
| Type: activityType, | ||
| CloudEventType: CloudEventType(activityType), | ||
| Description: desc, | ||
| Group: grp, | ||
| TeamScoped: true, | ||
| } | ||
| knownFilters[activityType] = filter{ | ||
| action: action, | ||
| resourceType: []ActivityLogEntryResourceType{resourceType}, | ||
|
|
||
| for _, opt := range opts { | ||
| opt(info) | ||
| } | ||
| rebuildReverseFilters() | ||
|
|
||
| eventTypeInfos[activityType] = *info | ||
| } |
| // groupPrefixes maps known multi-word prefixes to their display group names. | ||
| var groupPrefixes = map[string]string{ | ||
| "SERVICE_ACCOUNT": "Service Account", | ||
| "GENERIC_KUBERNETES_RESOURCE": "Kubernetes", | ||
| "OPENSEARCH": "OpenSearch", | ||
| "JOB_RUN": "Job", | ||
| } |
| activitylog.RegisterActivityType( | ||
| "GENERIC_KUBERNETES_RESOURCE_CREATED", | ||
| activitylog.ActivityLogEntryActionCreated, | ||
| ActivityLogEntryResourceTypeJob, | ||
| activitylog.WithDescription("Triggered when a generic Kubernetes resource is created."), | ||
| ) |
| activitylog.RegisterActivityType( | ||
| "GENERIC_KUBERNETES_RESOURCE_CREATED", | ||
| activitylog.ActivityLogEntryActionCreated, | ||
| ActivityLogEntryResourceTypeApplication, | ||
| activitylog.WithDescription("Triggered when a generic Kubernetes resource is created."), | ||
| ) |
| CREATE INDEX idx_webhook_subscriptions_team ON webhook_subscriptions (team_slug) | ||
| ; | ||
|
|
||
| CREATE INDEX idx_webhook_subscriptions_global ON webhook_subscriptions (id) | ||
| WHERE | ||
| team_slug IS NULL | ||
| ; |
|
Endringer: |
No description provided.